

#region Using Statements

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using System.Threading;
using System.ComponentModel;
using System.Reflection;
using System.Drawing;
using System.Windows.Forms;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Design;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using Freeform;
using FGame = Freeform.Game;
using XGame = Microsoft.Xna.Framework.Game;
using XViewport = Microsoft.Xna.Framework.Graphics.Viewport;
using FComponent = Freeform.Component;
using FViewport = Freeform.Viewport;
using FRandom = Freeform.Random;
using XColor = Microsoft.Xna.Framework.Color;

using Color = System.Drawing.Color;
using Rectangle = Microsoft.Xna.Framework.Rectangle;

#endregion


namespace Freeform
{
    /// <summary>
    /// Custom control uses the XNA Framework GraphicsDevice to render onto
    /// a Windows Form. Derived classes can override the Initialize and Draw
    /// methods to add their own drawing code.
    /// </summary>
    public class GameControl : Control, IGame
    {

        #region Members

        /// <summary>
        /// Gets the content manager for the control.
        /// </summary>
        public ContentManager Content { get; private set; }


        /// <summary>
        /// Gets a GraphicsDevice that can be used to draw onto this control.
        /// </summary>
        public GraphicsDevice GraphicsDevice { get { return GraphicsDeviceService.GraphicsDevice; } }


        /// <summary>
        /// Gets the Service Container for this Control.
        /// </summary>
        public ServiceContainer Services { get; private set; }

        /// <summary>
        /// Gets the graphics device manager for this game.
        /// </summary>
        public GraphicsDeviceManager GraphicsDeviceManager { get; private set; }


        /// <summary>
        /// Gets or sets the spritebatch used to render the viewports to screen.
        /// </summary>
        public SpriteBatch SpriteBatch { get; private set; }


        /// <summary>
        /// Gets the list of viewports in this game.
        /// </summary>
        public List<FViewport> Viewports { get; private set; }


        /// <summary>
        /// Gets the list of worlds in this game.
        /// </summary>
        public List<World> Worlds { get; private set; }


        /// <summary>
        /// Gets or sets the background colour of the game.
        /// </summary>
        public XColor BackgroundColour { get; set; }


        /// <summary>
        /// Gets the random number generator used in this game.
        /// </summary>
        public FRandom Random { get; private set; }


        /// <summary>
        /// Gets an average frames per second value for the game.
        /// </summary>
        public Int32 FPS { get; private set; }


        /// <summary>
        /// Gets the fraction of CPU load used by the game.
        /// </summary>
        public Single CPULoad { get; private set; }


        /// <summary>
        /// Gets the components that are controlled directly by the game.
        /// </summary>
        public ComponentList ComponentList { get; private set; }


        /// <summary>
        /// Gets the input manager for this game.
        /// </summary>
        public InputManager Input { get; private set; }


        /// <summary>
        /// Gets the currently active RenderTarget for the Graphics Device.
        /// </summary>
        public RenderTarget2D ActiveTarget { get; private set; }


        /// <summary>
        /// Gets the KeyBinder which can be used to bind keys for the game.
        /// </summary>
        public KeyBinder KeyBinder { get; private set; }


        /// <summary>
        /// Gets a reference to a dictionary that can be used to lookup any Entity in the game via its ID.
        /// </summary>
        public Dictionary<UInt64, Component> FreeformComponents { get; private set; }


        #region Private Implementation

        /// <summary>
        /// Counts the number of frames rendered each second.
        /// </summary>
        private Int32 Frames { get; set; }


        /// <summary>
        /// Counts down until the next FPS update.
        /// </summary>
        private Int32 NextFPSUpdate { get; set; }

        /// <summary>
        /// Stores the time when the Update() method begins.
        /// </summary>
        private DateTime Start { get; set; }


        /// <summary>
        /// Stores the time when the Draw() method ends.
        /// </summary>
        private DateTime Stop { get; set; }


        /// <summary>
        /// Stores the time span assosciated with the beginning of Update() and the end of Draw().
        /// </summary>
        private TimeSpan Running { get; set; }


        /// <summary>
        /// Stores the time spent idle between the end of Draw() and the beginning of Update().
        /// </summary>
        private TimeSpan Idle { get; set; }


        /// <summary>
        /// Gets the graphics device service for this control.
        /// </summary>
        private GraphicsDeviceService GraphicsDeviceService { get; set; }


        /// <summary>
        /// Gets the system time at which the application started.
        /// </summary>
        private DateTime AppStartTime { get; set; }


        /// <summary>
        /// Gets the system time at which the application last updated.
        /// </summary>
        private DateTime LastUpdate { get; set; }


        /// <summary>
        /// Gets or sets the thread used to carry out updates for the Game.
        /// </summary>
        private Thread UpdateThread { get; set; }


        /// <summary>
        /// Gets or sets the game time information.
        /// </summary>
        private GameTime GameTime { get; set; }


        private XViewport XViewport { get; set; }

        #endregion

        #endregion


        #region Init

        /// <summary>
        /// Initializes the control.
        /// </summary>
        protected override void OnCreateControl()
        {
            // Don't initialize the graphics device if we are running in the designer.
            if (DesignMode) { base.OnCreateControl(); return; }

            // Initialise the game service container.
            Services = new ServiceContainer();

            // Initialise the graphics device service.
            GraphicsDeviceService = GraphicsDeviceService.AddRef(Handle, ClientSize.Width, ClientSize.Height);

            // Register the service, so components like ContentManager can find it.
            Services.AddService<IGraphicsDeviceService>(GraphicsDeviceService);

            // Save application intialisation time.
            AppStartTime = DateTime.Now;
            LastUpdate = DateTime.Now;

            // Set game reference.
            Game.GameRef = this;

            // Setup content.
            Content = new ContentManager(Services, "Content");

            // Create list of worlds.
            Worlds = new List<World>();

            // Create random number generator.
            Random = new FRandom();

            // Create entity dictionary.
            FreeformComponents = new Dictionary<UInt64, FComponent>();

            // Create component list.
            ComponentList = new ComponentList(this);

            // Create list of viewports.
            Viewports = new List<FViewport>();

            // Set background colour.
            BackgroundColour = XColor.Black;

            // Create the input manager.
            Input = new InputManager(this);

            // Create the key binder.
            KeyBinder = new KeyBinder(this);

            // Give derived classes a chance to initialize themselves.
            Initialize();

            // Set graphics device settings.
            GraphicsDeviceService.ResetDevice(1600, 900);

            base.OnCreateControl();
        }

        #endregion


        #region Dispose

        /// <summary>
        /// Disposes the control.
        /// </summary>
        protected override void Dispose(bool Disposing)
        {
            if (UpdateThread != null && UpdateThread.IsAlive)
                UpdateThread.Abort();

            if (GraphicsDeviceService != null)
            {
                GraphicsDeviceService.Release(Disposing);
                GraphicsDeviceService = null;
            }

            base.Dispose(Disposing);
        }

        #endregion


        #region Paint

        /// <summary>
        /// Redraws the control in response to a WinForms paint message.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            //string beginDrawError = BeginDraw();
            BeginDraw();
            //if (string.IsNullOrEmpty(beginDrawError))
            {
                // Draw the control using the GraphicsDevice.
                if (!DesignMode)
                {
                    Draw(GameTime);
                    EndDraw();
                }
            }
            //else
            {
                // If BeginDraw failed, show an error message using System.Drawing.
                //PaintUsingSystemDrawing(e.Graphics, beginDrawError);
            }
        }


        /// <summary>
        /// Attempts to begin drawing the control. Returns an error message string
        /// if this was not possible, which can happen if the graphics device is
        /// lost, or if we are running inside the Form designer.
        /// </summary>
        void BeginDraw()
        {
            if (XViewport.Width != ClientSize.Width || XViewport.Height != ClientSize.Height)
            {
                XViewport = new XViewport(0, 0, ClientSize.Width, ClientSize.Height);
            }

            /*
            // If we have no graphics device, we must be running in the designer.
            if (GraphicsDeviceService == null)
            {
                return Text + "\n\n" + GetType();
            }

            // Make sure the graphics device is big enough, and is not lost.
            
            string deviceResetError = HandleDeviceReset();
            if (!string.IsNullOrEmpty(deviceResetError))
            {
                return deviceResetError;
            }


            // Many GraphicsDeviceControl instances can be sharing the same
            // GraphicsDevice. The device backbuffer will be resized to fit the
            // largest of these controls. But what if we are currently drawing
            // a smaller control? To avoid unwanted stretching, we set the
            // viewport to only use the top left portion of the full backbuffer.
            XViewport XViewport = new XViewport();

            XViewport = new XViewport(0, 0, ClientSize.Width, ClientSize.Height);
            XViewport.X = 0;
            XViewport.Y = 0;

            XViewport.Width = Width;
            XViewport.Height = Height;

            XViewport.MinDepth = 0;
            XViewport.MaxDepth = 1;
            
            return null;
             * */
        }


        /// <summary>
        /// Ends drawing the control. This is called after derived classes
        /// have finished their Draw method, and is responsible for presenting
        /// the finished image onto the screen, using the appropriate WinForms
        /// control handle to make sure it shows up in the right place.
        /// </summary>
        void EndDraw()
        {
            try
            {
                Rectangle sourceRectangle = new Rectangle(0, 0, ClientSize.Width, ClientSize.Height);

                GraphicsDevice.Present(sourceRectangle, null, this.Handle);
            }
            catch
            {
                // Present might throw if the device became lost while we were
                // drawing. The lost device will be handled by the next BeginDraw,
                // so we just swallow the exception.
            }
        }


        /// <summary>
        /// Helper used by BeginDraw. This checks the graphics device status,
        /// making sure it is big enough for drawing the current control, and
        /// that the device is not lost. Returns an error string if the device
        /// could not be reset.
        /// </summary>
        string HandleDeviceReset()
        {
            bool deviceNeedsReset = false;

            switch (GraphicsDevice.GraphicsDeviceStatus)
            {
                case GraphicsDeviceStatus.Lost:
                    // If the graphics device is lost, we cannot use it at all.
                    return "Graphics device lost";

                case GraphicsDeviceStatus.NotReset:
                    // If device is in the not-reset state, we should try to reset it.
                    deviceNeedsReset = true;
                    break;

                default:
                    // If the device state is ok, check whether it is big enough.
                    PresentationParameters pp = GraphicsDevice.PresentationParameters;

                    deviceNeedsReset = (ClientSize.Width > pp.BackBufferWidth) ||
                                       (ClientSize.Height > pp.BackBufferHeight);
                    break;
            }

            // Do we need to reset the device?
            if (deviceNeedsReset)
            {
                try
                {
                    GraphicsDeviceService.ResetDevice(ClientSize.Width, ClientSize.Height);
                }
                catch (Exception e)
                {
                    return "Graphics device reset failed\n\n" + e;
                }
            }

            return null;
        }


        /// <summary>
        /// If we do not have a valid graphics device (for instance if the device
        /// is lost, or if we are running inside the Form designer), we must use
        /// regular System.Drawing method to display a status message.
        /// </summary>
        protected virtual void PaintUsingSystemDrawing(Graphics graphics, string text)
        {
            graphics.Clear(Color.CornflowerBlue);

            using (Brush brush = new SolidBrush(Color.Black))
            {
                using (StringFormat format = new StringFormat())
                {
                    format.Alignment = StringAlignment.Center;
                    format.LineAlignment = StringAlignment.Center;

                    graphics.DrawString(text, Font, brush, ClientRectangle, format);
                }
            }
        }


        /// <summary>
        /// Ignores WinForms paint-background messages. The default implementation
        /// would clear the control to the current background color, causing
        /// flickering when our OnPaint implementation then immediately draws some
        /// other color over the top using the XNA Framework GraphicsDevice.
        /// </summary>
        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
        }


        #endregion


        #region Utility

        /// <summary>
        /// Returns the component of the given ID.
        /// </summary>
        /// <param name="ID">The ID of the component we would like to fetch.</param>
        /// <returns>A freeform component or null if the ID does not exist.</returns>
        public FComponent GetComponent(UInt64 ID)
        {
            FComponent Component;
            FreeformComponents.TryGetValue(ID, out Component);
            return Component;
        }


        /// <summary>
        /// Sets the RenderTarget for the graphics device.
        /// </summary>
        /// <param name="Target">The new Rendertarget for the graphics device.</param>
        public void SetRenderTarget(RenderTarget2D Target)
        {
            ActiveTarget = Target;
            GraphicsDevice.SetRenderTarget(Target);
        }


        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            if (GraphicsDeviceService != null)
                HandleDeviceReset();
        }

        #endregion


        #region Abstract Methods


        /// <summary>
        /// Derived classes override this to initialize their drawing code.
        /// </summary>
        protected virtual void Initialize() 
        {
            // Load sprite batch.
            SpriteBatch = new SpriteBatch(GraphicsDevice);

            Stop = DateTime.Now;
            NextFPSUpdate = 1000;
            GameTime = new GameTime();

            UpdateThread = new Thread(UpdateLoop);
            UpdateThread.IsBackground = true;
            UpdateThread.Priority = ThreadPriority.BelowNormal;
            UpdateThread.Start();
        }


        /// <summary>
        /// Derived classes override this to draw themselves using the GraphicsDevice.
        /// </summary>
        protected virtual void Draw(GameTime Time) 
        {
            // Allow each viewport to draw its scene.
            for (int i = 0; i < Viewports.Count; i++)
                if (Viewports[i].IsVisible && !Viewports[i].IsDisposed)
                    Viewports[i].Draw(Time);

            // Reset render target to compile final scene.
            SetRenderTarget(null);

            // Clear screen.
            GraphicsDevice.Clear(BackgroundColour);

            // Begin compilation scene.
            SpriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend);

            // Set correct viewport.
            GraphicsDevice.Viewport = XViewport;

            // Render each viewport to backbuffer.
            for (int i = 0; i < Viewports.Count; i++)
            {
                if (!Viewports[i].IsVisible || Viewports[i].IsDisposed) continue;

                // Calculate screen location for this viewport.
                Rectangle destination = new Rectangle((Int32)(Viewports[i].Position.X * GraphicsDevice.Viewport.Width),
                    (Int32)(Viewports[i].Position.Y * GraphicsDevice.Viewport.Height),
                    (Int32)(Viewports[i].Size.X * GraphicsDevice.Viewport.Width),
                    (Int32)(Viewports[i].Size.Y * GraphicsDevice.Viewport.Height));

                // Render viewport to screen.
                SpriteBatch.Draw(Viewports[i].RenderTarget, destination, null, Viewports[i].ColourOverlay, 0f, Vector2.Zero, SpriteEffects.None, Viewports[i].Depth);
            }

            // End scene.
            SpriteBatch.End();

            // Update FPS counter.
            Frames++;
            NextFPSUpdate -= Time.ElapsedGameTime.Milliseconds;
            if (NextFPSUpdate < 0)
            {
                NextFPSUpdate += 1000;
                FPS = Frames;
                Frames = 0;
            }

            // Update CPU load
            Stop = System.DateTime.Now;
            Running = Stop - Start;
            CPULoad = (Single)Running.Ticks / (Single)(Running.Ticks + Idle.Ticks);
        }


        /// <summary>
        /// Allows the component to update itself.
        /// </summary>
        /// <param name="Time">Provides a snapshot of timing values.</param>
        protected virtual void Update(GameTime Time) 
        {
            Start = System.DateTime.Now;
            Idle = Start - Stop;

            // Updates the component ordering and swaps in and out loaded / unloaded components.
            ComponentList.UpdateLists();

            // Allow each component to update itself.
            for (int i = 0; i < ComponentList.Loaded.Count; i++)
                if (ComponentList.Loaded[i].IsEnabled && !ComponentList.Loaded[i].IsDisposed)
                    ComponentList.Loaded[i].Update(Time);
        }


        /// <summary>
        /// An eternal process loop that calls update and draw on the Game.
        /// </summary>
        private void UpdateLoop()
        {
            DateTime Start;
            DateTime End = DateTime.Now;
            Int32 Millis;
            Thread.Sleep(250);
            while (true)
            {
                try
                {
                    Start = DateTime.Now;
                    GameTime = new GameTime(Start - AppStartTime, Start - End);
                    Update(GameTime);
                    Invalidate();
                    End = DateTime.Now;
                    Millis = (End - Start).Milliseconds;
                    if (Millis < 10)
                        Thread.Sleep(10 - Millis);
                }
                catch (Exception) { break; }
            }
        }

        #endregion

    }
}
